Popular Searches
Popular Course Categories
Popular Courses

JSON Data in Flutter

Flutter APIs & Networking

JSON Data in Flutter

JSON, or JavaScript Object Notation, is one of the most common formats used to exchange structured data between Flutter applications and backend servers. Flutter applications frequently receive JSON from REST APIs and convert that JSON into Dart objects. Flutter also uses JSON serialization when application data needs to be converted into a format suitable for transmission or storage.

Flutter applications can work with JSON using Dart's built-in dart:convert library. For larger applications, model classes and code-generation approaches such as json_serializable can help organize serialization and deserialization.


1. What is JSON?

JSON stands for JavaScript Object Notation. It is a lightweight text-based format used to represent structured data.

A simple JSON object looks like this:

{
  "name": "Rahul",
  "age": 25,
  "email": "[email protected]"
}

JSON is commonly used when a Flutter application communicates with a REST API.

Important Characteristics of JSON

  • JSON is text-based.
  • JSON represents structured data.
  • JSON is easy for humans to read.
  • JSON is easy for programs to parse.
  • JSON is commonly used in REST APIs.
  • JSON can represent objects, arrays, strings, numbers, booleans, and null values.

2. Why is JSON Used in Flutter?

JSON is commonly used in Flutter applications for communicating with backend services and representing structured information.

  • Fetching data from REST APIs.
  • Sending data to backend servers.
  • Representing users and products.
  • Handling authentication responses.
  • Storing structured configuration data.
  • Exchanging data between applications and services.
  • Converting API responses into Dart objects.

A typical Flutter application receives JSON from an API, decodes it, converts it into Dart models, and then uses those models to display information in the UI.


3. JSON Data Types

JSON supports several basic data types.

JSON Type Example Dart Equivalent
String "Rahul" String
Number 25 int / double
Boolean true bool
Object {"name":"Rahul"} Map
Array ["Flutter","Dart"] List
Null null Nullable Dart value

4. JSON Object

A JSON object contains key-value pairs inside curly braces {}.

{
  "id": 1,
  "name": "Rahul",
  "email": "[email protected]"
}

Each key is normally a string and is associated with a value.

JSON Object Structure

{
  "key": "value"
}

In Flutter, a decoded JSON object is commonly represented as a Dart Map.


5. JSON Array

A JSON array contains multiple values inside square brackets [].

[
  "Flutter",
  "Dart",
  "Firebase"
]

A JSON API can also return an array of objects:

[
  {
    "id": 1,
    "name": "Rahul"
  },
  {
    "id": 2,
    "name": "Priya"
  }
]

In Dart, a JSON array is commonly represented as a List.


6. Nested JSON Objects

JSON objects can contain other objects.

{
  "id": 1,
  "name": "Rahul",
  "address": {
    "city": "Mumbai",
    "country": "India"
  }
}

Nested JSON structures are common in real-world APIs and usually require nested Dart model classes for clean handling.

Nested JSON Flow

JSON Object
    ↓
User Object
    ↓
Address Object
    ↓
Address Model

7. JSON Arrays Inside Objects

An object can contain an array as one of its properties.

{
  "id": 1,
  "name": "Rahul",
  "skills": [
    "Dart",
    "Flutter",
    "Firebase"
  ]
}

In Dart, the skills field can be represented as a List.

final skills = json['skills'] as List;

8. JSON and Dart

Dart provides the dart:convert library for working with JSON.

import 'dart:convert';

The library provides functions such as:

  • jsonDecode() – converts JSON text into Dart data.
  • jsonEncode() – converts Dart data into JSON text.

The basic relationship is:

JSON String
    ↓
jsonDecode()
    ↓
Dart Map / List
Dart Map / List
    ↓
jsonEncode()
    ↓
JSON String

9. What is JSON Decoding?

Decoding means converting a JSON string received from an API or another source into Dart data structures.

JSON String
    ↓
jsonDecode()
    ↓
Map / List
    ↓
Dart Model
    ↓
Flutter UI

Decoding is mainly required when the application receives JSON data from a server.


10. Using jsonDecode()

The jsonDecode() function converts a JSON string into a Dart object such as a Map or List.

import 'dart:convert';

void main() {
  const jsonString = '''
  {
    "name": "Rahul",
    "age": 25
  }
  ''';

  final data = jsonDecode(jsonString);

  print(data['name']);
  print(data['age']);
}

When the JSON contains an object, the decoded result can be accessed using keys.


11. Decoding JSON into a Map

When a JSON object is returned, it can be represented as a Map.

final data = jsonDecode(jsonString) as Map;
print(data['name']);
print(data['age']);

Using an explicit map type provides clearer expectations about the structure of the decoded data.

Typed Map Example

final Map<String, dynamic> data =
    jsonDecode(jsonString) as Map<String, dynamic>;

12. Accessing JSON Values

final data = jsonDecode(jsonString) as Map<String, dynamic>;

final String name = data['name'] as String;
final int age = data['age'] as int;

print(name);
print(age);

For data coming from an external API, values should be validated or cast carefully because the server response may not always match the expected structure.


13. Decoding a JSON Array

If an API returns an array, jsonDecode() can produce a Dart List.

const jsonString = '''
[
  {
    "id": 1,
    "name": "Rahul"
  },
  {
    "id": 2,
    "name": "Priya"
  }
]
''';

final data = jsonDecode(jsonString) as List;

print(data[0]['name']);
print(data[1]['name']);

When the response contains multiple records, each element of the list can later be converted into a Dart model.


14. What is JSON Encoding?

Encoding is the process of converting Dart data into a JSON string.

Dart Object / Map
    ↓
jsonEncode()
    ↓
JSON String
    ↓
API Request

Encoding is commonly required when sending data to a backend API.


15. Using jsonEncode()

import 'dart:convert';

void main() {
  final user = {
    'name': 'Rahul',
    'age': 25,
    'email': '[email protected]',
  };

  final jsonString = jsonEncode(user);

  print(jsonString);
}

The resulting JSON represents the Dart map as JSON text.


16. Encoding a List

import 'dart:convert';

void main() {
  final skills = [
    'Dart',
    'Flutter',
    'Firebase',
  ];

  final jsonString = jsonEncode(skills);

  print(jsonString);
}

The result is a JSON array.


17. JSON Encoding for POST Requests

When sending JSON data to an API, the Dart data can be encoded using jsonEncode().

import 'dart:convert';
import 'package:http/http.dart' as http;

Future<void> createUser() async {
  final response = await http.post(
    Uri.parse('https://example.com/api/users'),
    headers: {
      'Content-Type': 'application/json',
    },
    body: jsonEncode({
      'name': 'Rahul',
      'email': '[email protected]',
    }),
  );

  print(response.statusCode);
}

The general sequence is:

Dart Map
    ↓
jsonEncode()
    ↓
JSON String
    ↓
HTTP POST
    ↓
Server

18. JSON with HTTP GET Requests

GET requests frequently receive JSON responses from APIs.

import 'dart:convert';
import 'package:http/http.dart' as http;

Future<void> fetchUser() async {
  final response = await http.get(
    Uri.parse('https://example.com/api/users/1'),
  );

  if (response.statusCode == 200) {
    final data = jsonDecode(response.body) as Map<String, dynamic>;

    print(data['name']);
  }
}

The typical process is:

HTTP GET
    ↓
HTTP Response
    ↓
response.body
    ↓
jsonDecode()
    ↓
Map / List
    ↓
Dart Model

19. Creating a Dart Model Class

A model class represents the structure of application data.

class User {
  final String name;
  final String email;

  User({
    required this.name,
    required this.email,
  });
}

A model provides a clear structure for data instead of accessing JSON map values throughout the application.

Why Use Models?

  • Improves readability.
  • Provides a predictable data structure.
  • Improves type safety.
  • Centralizes JSON conversion logic.
  • Makes application code easier to maintain.

20. Using fromJson()

A fromJson() factory constructor can convert a JSON map into a Dart object.

class User {
  final String name;
  final String email;

  User({
    required this.name,
    required this.email,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      name: json['name'] as String,
      email: json['email'] as String,
    );
  }
}

The basic flow is:

JSON Map
    ↓
User.fromJson()
    ↓
User Object

21. Converting JSON into a Dart Object

import 'dart:convert';

void main() {
  const jsonString = '''
  {
    "name": "Rahul",
    "email": "[email protected]"
  }
  ''';

  final userMap =
      jsonDecode(jsonString) as Map<String, dynamic>;

  final user = User.fromJson(userMap);

  print(user.name);
  print(user.email);
}

The JSON map is first decoded and then passed to User.fromJson().


22. Using toJson()

The toJson() method performs the reverse operation. It converts a Dart model object into a map that can be encoded as JSON.

class User {
  final String name;
  final String email;

  User({
    required this.name,
    required this.email,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      name: json['name'] as String,
      email: json['email'] as String,
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'email': email,
    };
  }
}

23. Encoding a Dart Model

import 'dart:convert';

final user = User(
  name: 'Rahul',
  email: '[email protected]',
);

final jsonString = jsonEncode(user.toJson());

print(jsonString);

The serialization flow is:

User Object
    ↓
toJson()
    ↓
Map
    ↓
jsonEncode()
    ↓
JSON String

24. Complete fromJson() and toJson() Example

class Product {
  final int id;
  final String name;
  final double price;

  Product({
    required this.id,
    required this.name,
    required this.price,
  });

  factory Product.fromJson(Map<String, dynamic> json) {
    return Product(
      id: json['id'] as int,
      name: json['name'] as String,
      price: (json['price'] as num).toDouble(),
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'price': price,
    };
  }
}

This pattern can be reused for most API model classes.


25. JSON List to List of Models

When an API returns multiple objects, each JSON object can be converted into a model.

final List<dynamic> data =
    jsonDecode(response.body) as List;

final users = data
    .map(
      (item) => User.fromJson(
        item as Map<String, dynamic>,
      ),
    )
    .toList();

The result is a list of User objects.


26. Strongly Typed JSON Lists

You can explicitly declare the resulting model list.

final List<User> users =
    (jsonDecode(response.body) as List)
        .map(
          (item) => User.fromJson(
            item as Map<String, dynamic>,
          ),
        )
        .toList();

This makes the expected data type clear to the rest of the application.

Conversion Flow

JSON Array
    ↓
List
    ↓
Map for each item
    ↓
User.fromJson()
    ↓
List<User>

27. Nested JSON and Model Classes

Consider the following JSON:

{
  "name": "Rahul",
  "address": {
    "city": "Mumbai",
    "country": "India"
  }
}

Create a separate model for the nested object.

class Address {
  final String city;
  final String country;

  Address({
    required this.city,
    required this.country,
  });

  factory Address.fromJson(Map<String, dynamic> json) {
    return Address(
      city: json['city'] as String,
      country: json['country'] as String,
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'city': city,
      'country': country,
    };
  }
}

Then use it inside the User model:

class User {
  final String name;
  final Address address;

  User({
    required this.name,
    required this.address,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      name: json['name'] as String,
      address: Address.fromJson(
        json['address'] as Map<String, dynamic>,
      ),
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'address': address.toJson(),
    };
  }
}

Nested Model Flow

JSON
 ↓
User.fromJson()
 ↓
Address.fromJson()
 ↓
Address Object
 ↓
User Object

28. JSON with Null Values

API responses can contain null values.

{
  "name": "Rahul",
  "phone": null
}

If a field can be missing or null, model it as nullable when appropriate.

class User {
  final String name;
  final String? phone;

  User({
    required this.name,
    this.phone,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      name: json['name'] as String,
      phone: json['phone'] as String?,
    );
  }
}

The ? indicates that the Dart value may be null.


29. Handling Optional JSON Fields

Some APIs may not always return every field.

{
  "name": "Rahul"
}

When a field may be absent, the model should account for that possibility.

class User {
  final String name;
  final String? phone;

  User({
    required this.name,
    this.phone,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      name: json['name'] as String,
      phone: json['phone'] as String?,
    );
  }
}

Nullable fields help prevent assumptions that a value will always be present.


30. JSON Type Conversion

JSON numbers may need careful handling when converting them to Dart numeric types.

For example, when an API may return an integer or decimal number, num can be converted to double.

final price = (json['price'] as num).toDouble();

This is useful when the API's numeric representation may vary between whole and decimal values.


31. JSON Boolean Values

JSON supports boolean values using true and false.

{
  "name": "Flutter",
  "isActive": true
}

Dart model:

class Course {
  final String name;
  final bool isActive;

  Course({
    required this.name,
    required this.isActive,
  });

  factory Course.fromJson(Map<String, dynamic> json) {
    return Course(
      name: json['name'] as String,
      isActive: json['isActive'] as bool,
    );
  }
}

32. JSON with HTTP Response

JSON is frequently found inside the body of an HTTP response.

final response = await http.get(
  Uri.parse('https://example.com/api/users'),
);

if (response.statusCode == 200) {
  final data = jsonDecode(response.body);
  print(data);
}

The typical Flutter networking flow is to make the HTTP request, check the response status code, decode the response body, and convert the resulting data into a Dart model.


33. JSON Response Error Handling

JSON parsing should generally happen only after confirming that the HTTP request returned an appropriate response.

Future<User> fetchUser() async {
  final response = await http.get(
    Uri.parse('https://example.com/api/users/1'),
  );

  if (response.statusCode == 200) {
    final data =
        jsonDecode(response.body) as Map<String, dynamic>;

    return User.fromJson(data);
  }

  throw Exception(
    'Failed to load user: ${response.statusCode}',
  );
}

Checking the status code before parsing helps separate successful responses from HTTP errors.


34. JSON FormatException

If the server returns malformed JSON, decoding can fail.

try {
  final data = jsonDecode(response.body);
  print(data);
} on FormatException catch (e) {
  print('Invalid JSON: $e');
} catch (e) {
  print('Unexpected error: $e');
}

This helps prevent malformed response data from causing an unhandled application error.


35. Manual JSON Serialization

For smaller projects, JSON serialization can be implemented manually using fromJson() and toJson().

class User {
  final String name;
  final String email;

  User({
    required this.name,
    required this.email,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      name: json['name'] as String,
      email: json['email'] as String,
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'email': email,
    };
  }
}

Manual serialization is straightforward and is often useful when an application contains only a small number of models.


36. Advantages of Model-Based JSON Parsing

  • Provides a clear data structure.
  • Improves code readability.
  • Provides better type safety.
  • Provides better autocomplete.
  • Reduces repeated JSON field access.
  • Keeps parsing logic inside the model.
  • Makes API data easier to use throughout the application.

Raw Map vs Model

Approach Characteristics
Raw JSON Map Quick but less structured
Dart Model Structured and easier to maintain
Generated Model Reduces repetitive serialization code

37. JSON Serialization with Code Generation

For medium and large applications with many models, manually writing serialization code can become repetitive. Code generation provides an alternative approach.

One commonly used solution is json_serializable.

Required Packages

dependencies:
  json_annotation: <current-version>

dev_dependencies:
  build_runner: <current-version>
  json_serializable: <current-version>

Use the appropriate current package versions for your project rather than copying a fixed version number from an old example.


38. Using json_serializable

A model can be annotated so that serialization code is generated.

import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';

@JsonSerializable()
class User {
  User(this.name, this.email);

  String name;
  String email;

  factory User.fromJson(
    Map<String, dynamic> json,
  ) => _$UserFromJson(json);

  Map<String, dynamic> toJson() => _$UserToJson(this);
}

The generated file contains the serialization boilerplate.

How It Works

User Model
    ↓
@JsonSerializable()
    ↓
build_runner
    ↓
Generated user.g.dart
    ↓
fromJson() / toJson()

39. Generating JSON Serialization Code

After configuring json_serializable, the Dart build runner can generate the required code.

dart run build_runner build --delete-conflicting-outputs

For continuous generation while developing:

dart run build_runner watch --delete-conflicting-outputs

The watcher can automatically regenerate serialization code when model files change.


40. Manual Serialization vs Code Generation

Feature Manual Serialization Code Generation
Setup Simple Requires configuration
Dependencies Uses dart:convert Uses generation packages
Small projects Suitable May be unnecessary
Large projects Can become repetitive Scales well
Boilerplate Written manually Generated
Maintenance Manual Generated

The appropriate approach depends on the size, number of models, and complexity of the application.


41. JSON Field Naming

APIs sometimes use naming conventions such as snake_case.

Example JSON:

{
  "first_name": "Rahul",
  "last_name": "Sharma"
}

Your Dart model may use camelCase:

final String firstName;
final String lastName;

When using json_serializable, annotations such as @JsonKey can map JSON field names to Dart properties.

Example

@JsonKey(name: 'first_name')
final String firstName;

42. JSON Field Rename with json_serializable

For a model with multiple snake_case fields, json_serializable can also use a field renaming strategy.

@JsonSerializable(
  fieldRename: FieldRename.snake,
)
class User {
  User(this.firstName, this.lastName);

  final String firstName;
  final String lastName;

  factory User.fromJson(
    Map<String, dynamic> json,
  ) => _$UserFromJson(json);

  Map<String, dynamic> toJson() => _$UserToJson(this);
}

This can automatically map Dart camelCase fields to JSON snake_case fields.


43. JSON Default Values

Sometimes an API may omit a field or return null. Code-generation annotations can provide default values for selected fields.

@JsonKey(defaultValue: false)
final bool isActive;

Default values can be useful when an application expects a value even when the API does not provide one.


44. Validating JSON Data

External API data should not automatically be assumed to have the expected structure.

For example, a model can validate the types of important fields:

factory User.fromJson(Map<String, dynamic> json) {
  if (json['name'] is! String ||
      json['email'] is! String) {
    throw const FormatException(
      'Invalid user JSON',
    );
  }

  return User(
    name: json['name'] as String,
    email: json['email'] as String,
  );
}

Validation can help identify unexpected API responses earlier in the data-processing flow.


45. JSON and API Architecture

In a larger Flutter application, JSON parsing can be separated into different layers.

Flutter UI
    ↓
ViewModel / State Management
    ↓
Repository
    ↓
API Service
    ↓
HTTP Request
    ↓
JSON Response
    ↓
jsonDecode()
    ↓
Dart Model
    ↓
Application State
    ↓
Flutter UI

This structure keeps API communication and JSON parsing separate from presentation code.

Responsibilities of Each Layer

Layer Responsibility
UI Displays application data
ViewModel / State Manages UI state and actions
Repository Coordinates data sources
API Service Communicates with backend services
Model Represents structured data
JSON Parser Converts JSON and Dart data

46. Complete JSON API Example

import 'dart:convert';
import 'package:http/http.dart' as http;

class User {
  final int id;
  final String name;
  final String email;

  User({
    required this.id,
    required this.name,
    required this.email,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      email: json['email'] as String,
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'email': email,
    };
  }
}

Future<User> fetchUser() async {
  final response = await http.get(
    Uri.parse(
      'https://example.com/api/users/1',
    ),
    headers: {
      'Accept': 'application/json',
    },
  );

  if (response.statusCode == 200) {
    final json =
        jsonDecode(response.body) as Map<String, dynamic>;

    return User.fromJson(json);
  }

  throw Exception('Failed to load user');
}

Complete Flow

Flutter
   ↓
fetchUser()
   ↓
HTTP GET
   ↓
Server
   ↓
JSON Response
   ↓
jsonDecode()
   ↓
User.fromJson()
   ↓
User Object
   ↓
Flutter UI

47. JSON with POST Request

The following example demonstrates converting a Dart map into JSON before sending it to a server.

Future<User> createUser(
  String name,
  String email,
) async {
  final response = await http.post(
    Uri.parse(
      'https://example.com/api/users',
    ),
    headers: {
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode({
      'name': name,
      'email': email,
    }),
  );

  if (response.statusCode == 201) {
    final json =
        jsonDecode(response.body) as Map<String, dynamic>;

    return User.fromJson(json);
  }

  throw Exception('Failed to create user');
}

The complete sequence is:

Dart Data
    ↓
jsonEncode()
    ↓
JSON Request Body
    ↓
HTTP POST
    ↓
Server
    ↓
JSON Response
    ↓
jsonDecode()
    ↓
User.fromJson()
    ↓
User Object

48. JSON Data in FutureBuilder

JSON-based API data can be displayed using FutureBuilder.

Future<User> fetchUser() async {
  final response = await http.get(
    Uri.parse(
      'https://example.com/api/users/1',
    ),
  );

  if (response.statusCode == 200) {
    return User.fromJson(
      jsonDecode(response.body)
          as Map<String, dynamic>,
    );
  }

  throw Exception('Failed to load user');
}

UI example:

FutureBuilder<User>(
  future: fetchUser(),
  builder: (context, snapshot) {
    if (snapshot.connectionState ==
        ConnectionState.waiting) {
      return const CircularProgressIndicator();
    }

    if (snapshot.hasError) {
      return Text(
        'Error: ${snapshot.error}',
      );
    }

    if (snapshot.hasData) {
      final user = snapshot.data!;

      return Column(
        children: [
          Text(user.name),
          Text(user.email),
        ],
      );
    }

    return const Text('No user found');
  },
)

FutureBuilder Data Flow

Future
  ↓
Waiting
  ↓
HTTP Request
  ↓
JSON Response
  ↓
Dart Model
  ↓
FutureBuilder
  ↓
Flutter UI

49. Parsing Large JSON Data

Large JSON responses can require significant processing. When JSON parsing becomes expensive enough to affect UI responsiveness, parsing work can be moved away from the main UI isolate.

Typical Architecture

HTTP Response
     ↓
Large JSON String
     ↓
Background Isolate
     ↓
JSON Parsing
     ↓
Dart Models
     ↓
Main Flutter UI

For normal-sized API responses, regular decoding may be sufficient. Background parsing is mainly relevant when the amount of data or parsing work is large enough to cause noticeable UI work.


50. JSON Best Practices

  1. Use dart:convert for basic JSON encoding and decoding.
  2. Use jsonDecode() for decoding JSON strings.
  3. Use jsonEncode() for encoding Dart data.
  4. Use model classes for structured application data.
  5. Keep fromJson() and toJson() logic organized.
  6. Validate external data when appropriate.
  7. Handle nullable fields correctly.
  8. Check HTTP status codes before decoding successful API responses.
  9. Use code generation for larger projects when manual serialization becomes repetitive.
  10. Keep JSON parsing separate from UI code.
  11. Use background parsing for sufficiently large JSON payloads when needed.
  12. Write tests for important serialization and deserialization logic.

51. Common JSON Mistakes

  • Forgetting to import dart:convert.
  • Using jsonEncode() when you actually need jsonDecode().
  • Using jsonDecode() when you need to send JSON.
  • Assuming every API field is always present.
  • Ignoring nullable fields.
  • Assuming an API number is always an int when it may be decimal.
  • Accessing deeply nested JSON without checking its structure.
  • Putting raw JSON map access throughout the UI.
  • Not checking the HTTP response status code.
  • Not handling malformed JSON.
  • Manually writing large amounts of repetitive serialization code.

Common Mistake: Confusing Encode and Decode

Function Direction Typical Usage
jsonDecode() JSON → Dart Reading API responses
jsonEncode() Dart → JSON Sending API requests

52. JSON Serialization Flow

Dart Object
    ↓
toJson()
    ↓
Map
    ↓
jsonEncode()
    ↓
JSON String
    ↓
HTTP Request
    ↓
Server

53. JSON Deserialization Flow

Server
    ↓
JSON Response
    ↓
HTTP Response Body
    ↓
jsonDecode()
    ↓
Map / List
    ↓
fromJson()
    ↓
Dart Model
    ↓
Flutter UI

54. JSON Quick Revision

Concept Purpose
JSON Structured data exchange format
dart:convert Dart library for JSON conversion
jsonDecode() JSON string to Dart data
jsonEncode() Dart data to JSON string
fromJson() JSON map to Dart model
toJson() Dart model to JSON-compatible map
json_serializable Code generation for JSON serialization
Model class Represents structured application data

55. Practice Exercise

Create a Flutter application that retrieves user data from an API and displays it on the screen.

Requirements

  • Create a User model.
  • Add fromJson().
  • Add toJson().
  • Send a GET request.
  • Decode the JSON response.
  • Convert JSON into a User object.
  • Display the user's name and email.
  • Show a loading indicator while data is being fetched.
  • Show an error message if JSON parsing or the network request fails.

Advanced Exercise

  • Create a list of users.
  • Parse a JSON array into List.
  • Add a POST form for creating a user.
  • Convert the user model to JSON.
  • Send the JSON using http.post().
  • Parse the returned JSON response.

Mini Project Flow

User Screen
    ↓
Fetch Button
    ↓
HTTP GET
    ↓
JSON Response
    ↓
jsonDecode()
    ↓
User.fromJson()
    ↓
User Object
    ↓
Display User

56. Interview Questions

Q1. What is JSON?

JSON is a lightweight text-based format commonly used to represent and exchange structured data.

Q2. Which Dart library is used for JSON encoding and decoding?

The built-in dart:convert library provides JSON encoding and decoding functionality.

Q3. What does jsonDecode() do?

jsonDecode() converts a JSON string into Dart data such as a Map or List.

Q4. What does jsonEncode() do?

jsonEncode() converts Dart data into a JSON string.

Q5. What is fromJson()?

fromJson() is commonly implemented as a factory constructor that creates a Dart model object from a JSON-compatible map.

Q6. What is toJson()?

toJson() converts a Dart model object into a JSON-compatible map.

Q7. Why use model classes instead of raw JSON maps?

Model classes provide clearer structure, stronger typing, better autocomplete, and centralized serialization logic.

Q8. When should code generation be considered?

Code generation can be useful when an application has many JSON models and manually maintaining serialization code becomes repetitive.

Q9. What is the difference between serialization and deserialization?

Serialization converts Dart data into a JSON-compatible representation, while deserialization converts JSON data into Dart objects.

Q10. What is the difference between jsonEncode() and jsonDecode()?

Function Input Output
jsonEncode() Dart data JSON string
jsonDecode() JSON string Dart data

57. Recommended JSON Development Process

  1. Understand the API response structure.
  2. Create Dart model classes.
  3. Implement fromJson().
  4. Implement toJson() when data must be sent back to the server.
  5. Make nullable fields nullable in Dart when appropriate.
  6. Perform HTTP requests through a service or repository layer.
  7. Check HTTP status codes.
  8. Decode the JSON response.
  9. Convert JSON maps into model objects.
  10. Expose model data to the application state.
  11. Display the model data in Flutter widgets.
  12. Handle loading, empty, parsing, and network error states.
  13. Use code generation when the number of models makes manual serialization repetitive.

Development Flow

API Documentation
      ↓
JSON Structure
      ↓
Dart Model
      ↓
fromJson()
      ↓
API Service
      ↓
Repository
      ↓
State Management
      ↓
Flutter UI

58. Official Flutter JSON Resources


59. Learn Flutter with JustAcademy

For structured Flutter training and practical learning, explore the following resources:

JustAcademy Flutter Training Course

Register for Flutter Course Demo


60. Learning Outcome

After studying this topic, you should be able to:

  • Understand the purpose and structure of JSON.
  • Identify common JSON data types.
  • Decode JSON using jsonDecode().
  • Encode Dart data using jsonEncode().
  • Work with JSON objects and arrays.
  • Create Dart model classes for API responses.
  • Implement fromJson() and toJson().
  • Handle nested JSON structures.
  • Handle nullable and optional JSON fields.
  • Convert JSON arrays into lists of Dart models.
  • Work with JSON in HTTP GET and POST requests.
  • Handle JSON parsing and HTTP errors.
  • Use json_serializable for generated serialization code.
  • Organize JSON processing into a maintainable application architecture.

61. Summary

JSON is an important part of Flutter application development because it provides a common structure for exchanging data between Flutter applications and backend services. The dart:convert library provides jsonDecode() for converting JSON strings into Dart data and jsonEncode() for converting Dart data into JSON strings.

For simple applications, manual serialization using fromJson() and toJson() is straightforward. For larger applications with many models, code generation with tools such as json_serializable can reduce repetitive serialization code.

The overall JSON workflow can be remembered as:

API Response
    ↓
JSON String
    ↓
jsonDecode()
    ↓
Map / List
    ↓
fromJson()
    ↓
Dart Model
    ↓
State Management
    ↓
Flutter UI

For sending data:

Flutter Data
    ↓
Dart Model
    ↓
toJson()
    ↓
Map
    ↓
jsonEncode()
    ↓
JSON String
    ↓
HTTP Request
    ↓
Backend Server
Previous Next
whatsapp